import { ICircle, IRect, IVec2 } from "../util/Defined";
import { Rect } from "./math/Rect";
import { Vec2 } from "./math/Vec2";

const T1 = new Vec2();
const T2 = new Vec2();
const T3 = new Vec2();
const T4 = new Vec2();

/**
 * 辅助类，用于测试形状与形状是否相交
 * by:cocos-engine
 * https://github.com/cocos/cocos-engine
 */
export class Intersection {

    /**
     * 测试线段与线段是否相交
     */
    static lineLine(a1: Readonly<IVec2>, a2: Readonly<IVec2>, b1: Readonly<IVec2>, b2: Readonly<IVec2>): boolean {
        const ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x);
        const ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x);
        const u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);

        if (u_b !== 0) {
            const ua = ua_t / u_b;
            const ub = ub_t / u_b;

            if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
                return true;
            }
        }

        return false;
    }

    /**
     * 测试线段与矩形是否相交
     */
    static lineRect(a1: Readonly<IVec2>, a2: Readonly<IVec2>, b: Readonly<IRect>): boolean {
        const r0 = T1.setPos(b.x, b.y);
        const r1 = T2.setPos(b.x, b.y + b.height);
        const r2 = T3.setPos(b.x + b.width, b.y + b.height);
        const r3 = T4.setPos(b.x + b.width, b.y);

        if (this.lineLine(a1, a2, r0, r1)) return true;

        if (this.lineLine(a1, a2, r1, r2)) return true;

        if (this.lineLine(a1, a2, r2, r3)) return true;

        if (this.lineLine(a1, a2, r3, r0)) return true;

        return false;
    }

    /**
     * 测试线段与多边形是否相交
     */
    static linePolygon(a1: Readonly<IVec2>, a2: Readonly<IVec2>, b: readonly Vec2[]): boolean {
        const length = b.length;

        for (let i = 0; i < length; ++i) {
            const b1 = b[i];
            const b2 = b[(i + 1) % length];

            if (this.lineLine(a1, a2, b1, b2)) return true;
        }

        return false;
    }

    /**
     * 测试矩形与矩形是否相交
     */
    static rectRect(a: Readonly<IRect>, b: Readonly<IRect>): boolean {
        const a_min_x = a.x;
        const a_min_y = a.y;
        const a_max_x = a.x + a.width;
        const a_max_y = a.y + a.height;

        const b_min_x = b.x;
        const b_min_y = b.y;
        const b_max_x = b.x + b.width;
        const b_max_y = b.y + b.height;

        return a_min_x <= b_max_x
            && a_max_x >= b_min_x
            && a_min_y <= b_max_y
            && a_max_y >= b_min_y;
    }

    /**
     * 测试矩形与多边形是否相交
     */
    static rectPolygon(a: Readonly<IRect>, b: readonly Vec2[]): boolean {
        const r0 = T1.setPos(a.x, a.y);
        const r1 = T2.setPos(a.x, a.y + a.height);
        const r2 = T3.setPos(a.x + a.width, a.y + a.height);
        const r3 = T4.setPos(a.x + a.width, a.y);

        // intersection check
        if (this.linePolygon(r0, r1, b)) return true;

        if (this.linePolygon(r1, r2, b)) return true;

        if (this.linePolygon(r2, r3, b)) return true;

        if (this.linePolygon(r3, r0, b)) return true;

        // check if a contains b
        for (let i = 0, l = b.length; i < l; ++i) {
            if (Rect.containsPos(a, b[i])) return true;
        }

        // check if b contains a
        if (this.pointInPolygon(r0, b)) return true;

        if (this.pointInPolygon(r1, b)) return true;

        if (this.pointInPolygon(r2, b)) return true;

        if (this.pointInPolygon(r3, b)) return true;

        return false;
    }

    /**
     * 测试多边形与多边形是否相交
     */
    static polygonPolygon(a: readonly Vec2[], b: readonly Vec2[]): boolean {
        let i: number; let l: number;

        // check if a intersects b
        for (i = 0, l = a.length; i < l; ++i) {
            const a1 = a[i];
            const a2 = a[(i + 1) % l];

            if (this.linePolygon(a1, a2, b)) return true;
        }

        // check if a contains b
        for (i = 0, l = b.length; i < l; ++i) {
            if (this.pointInPolygon(b[i], a)) return true;
        }

        // check if b contains a
        for (i = 0, l = a.length; i < l; ++i) {
            if (this.pointInPolygon(a[i], b)) return true;
        }

        return false;
    }

    /**
     * 测试圆形与圆形是否相交
     */
    static circleCircle(c1p: Readonly<IVec2>, c1r: number, c2p: Readonly<IVec2>, c2r: number): boolean {
        const distance = Vec2.dis(c1p, c2p);
        return distance < (c1r + c2r);
    }

    /**
     * 测试圆形与圆形是否相交
     */
    static circleCircle2(circle1: Readonly<ICircle>, circle2: Readonly<ICircle>) {
        return this.circleCircle(circle1, circle1.radius, circle2, circle2.radius);
    }

    /**
     * 测试多边形与圆形是否相交
     */
    static polygonCircle(polygon: readonly Vec2[], cp: Readonly<IVec2>, cr: number): boolean {
        const position = cp;
        if (this.pointInPolygon(position, polygon)) {
            return true;
        }

        for (let i = 0, l = polygon.length; i < l; i++) {
            const start = i === 0 ? polygon[polygon.length - 1] : polygon[i - 1];
            const end = polygon[i];

            if (this.pointLineDistance(position, start, end, true) < cr) {
                return true;
            }
        }

        return false;
    }

    /**
     * 测试多边形与圆形是否相交
     */
    static polygonCircle2(polygon: readonly Vec2[], circle: Readonly<ICircle>): boolean {
        return this.polygonCircle(polygon, circle, circle.radius);
    }

    /**
     * 测试矩形与圆形是否相交
     */
    static rectCircle(rect: Readonly<IRect>, cp: Readonly<IVec2>, cr: number): boolean {
        const cx = cp.x;
        const cy = cp.y;

        const rx = rect.x;
        const ry = rect.y;
        const rw = rect.width;
        const rh = rect.height;

        // temporary variables to set edges for testing
        let testX = cx;
        let testY = cy;

        // which edge is closest?
        if (cx < rx) testX = rx;      // test left edge
        else if (cx > rx + rw) testX = rx + rw;   // right edge
        if (cy < ry) testY = ry;      // top edge
        else if (cy > ry + rh) testY = ry + rh;   // bottom edge

        // get distance from closest edges
        const distX = cx - testX;
        const distY = cy - testY;
        const distance = Math.sqrt((distX * distX) + (distY * distY));

        // if the distance is less than the radius, collision!
        if (distance <= cr) {
            return true;
        }
        return false;
    }

    /**
     * 测试矩形与圆形是否相交
     */
    static rectCircle2(rect: Readonly<IRect>, circle: Readonly<ICircle>): boolean {
        return this.rectCircle(rect, circle, circle.radius);
    }

    /**
     * 测试一个点是否在一个多边形中
     */
    static pointInPolygon(point: Readonly<IVec2>, polygon: readonly Vec2[]): boolean {
        let inside = false;
        const x = point.x;
        const y = point.y;

        // use some raycasting to test hits
        // https://github.com/substack/point-in-polygon/blob/master/index.js
        const length = polygon.length;

        for (let i = 0, j = length - 1; i < length; j = i++) {
            const xi = polygon[i].x; const yi = polygon[i].y;
            const xj = polygon[j].x; const yj = polygon[j].y;
            const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);

            if (intersect) inside = !inside;
        }

        return inside;
    }

    /**
     * 计算点到直线的距离。如果这是一条线段并且垂足不在线段内，则会计算点到线段端点的距离。
     */
    static pointLineDistance(point: Readonly<IVec2>, start: Readonly<IVec2>, end: Readonly<IVec2>, isSegment: boolean): number {
        let dx = end.x - start.x;
        let dy = end.y - start.y;
        const d = dx * dx + dy * dy;
        const t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / d;
        let p: IVec2;

        if (!isSegment) {
            p = T1.setPos(start.x + t * dx, start.y + t * dy);
        } else if (d) {
            if (t < 0) p = start;
            else if (t > 1) p = end;
            else p = T1.setPos(start.x + t * dx, start.y + t * dy);
        } else {
            p = start;
        }

        dx = point.x - p.x;
        dy = point.y - p.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    /**
     * 点是否在圆内
     */
    static pointInCircle(pos: Readonly<IVec2>, circle: Readonly<ICircle>) {
        return Vec2.dis(pos, circle) < circle.radius;
    }

    /**
     * 点是否在圆内
     */
    static pointInCircle2(x: number, y: number, circle: Readonly<ICircle>) {
        return T1.setPos(x, y).dis(circle) < circle.radius;
    }

    /**
     * 圆1是否在圆2内
     */
    static circleInCircle(c1: Readonly<ICircle>, c2: Readonly<ICircle>) {
        if (c1.radius > c2.radius) return false;
        return Vec2.dis(c1, c2) < c2.radius - c1.radius;
    }

    /**
     * 矩形是否在园内
     */
    static rectInCircle(rect: Readonly<IRect>, circle: Readonly<ICircle>) {
        return this.pointInCircle(rect, circle)
            && this.pointInCircle2(rect.x, rect.y + rect.height, circle)
            && this.pointInCircle2(rect.x + rect.width, rect.y, circle)
            && this.pointInCircle2(rect.x + rect.width, rect.y + rect.height, circle);
    }

    /**
     * 多边形是否在园内
     */
    static polygonInCircle(polygon: readonly Readonly<IVec2>[], circle: Readonly<ICircle>) {
        for (let i = 0; i < polygon.length; ++i) {
            if (!this.pointInCircle(polygon[i], circle)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 线段与圆是否相交
     */
    static lineCircle(p1: Readonly<IVec2>, p2: Readonly<IVec2>, circle: Readonly<ICircle>) {
        // 计算线段的起点到终点的方向向量
        const lineDir = Vec2.dirTo(p1, p2, T1);

        // 计算线段的起点到圆心的向量
        const lineToCircle = T2.setPos(circle.x - p1.x, circle.y - p1.y);

        // 计算线段的起点到圆心的投影长度
        const projLen = lineToCircle.x * lineDir.x + lineToCircle.y * lineDir.y;

        // 如果投影长度小于0，表示圆在线段的起点的背面，不相交
        if (projLen < 0) {
            return false
        } else {
            // 计算圆心到投影点的距离
            const dis = Math.sqrt(
                (lineToCircle.x - projLen * lineDir.x) ** 2 +
                (lineToCircle.y - projLen * lineDir.y) ** 2
            );

            // 如果距离小于等于圆的半径，表示线段与圆相交
            if (dis <= circle.radius) {
                return true
            } else {
                return false
            }
        }
    }

}